/*----------------------------------------------------------------------------

	Copyright (c) 2006, Creator of the Cant Project.
	All rights reserved.

	Redistribution and use in source and binary forms, with or without
	modification, are permitted provided that the following conditions are met:

	+ Redistributions of source code must retain the above copyright notice,
	this list of conditions and the following disclaimer.

	+ Redistributions in binary form must reproduce the above copyright notice,
	this list of conditions and the following disclaimer in the documentation
	and/or other materials provided with the distribution.

	+ Neither the name of the Creator of the Cant Project nor the names of its
	contributors may be used to endorse or promote products derived from this
	software without specific prior written permission.

	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
	ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
	LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
	CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
	POSSIBILITY OF SUCH DAMAGE.

----------------------------------------------------------------------------*/

#include	"project.hpp"



/**
	Constant.
	
	Tag name.
	
*/
const std::string		Project::STR_TAG_PROJECT			= "project";
const std::string		Project::STR_TAG_INCLUDE			= "include";
const std::string		Project::STR_TAG_PROPERTY			= "property";
const std::string		Project::STR_TAG_TARGET				= "target";


/**
	Constant.
	
	Attribute name.
	
*/
const std::string		Project::STR_ATTRIB_BASEDIR			= "basedir";
const std::string		Project::STR_ATTRIB_DEFAULT			= "default";
const std::string		Project::STR_ATTRIB_DESCRIPTION		= "description";
const std::string		Project::STR_ATTRIB_NAME			= "name";
const std::string		Project::STR_ATTRIB_INCLUDE_PATH	= "path";


/**
	Constant.
	
	Text string.
*/
const std::string		Project::STR_PROPERTY_START			= "${";
const std::string		Project::STR_PROPERTY_END			= "}";

const std::string		Project::STR_EMPTY					= "";


/**
	Shared list of loaded projects.
	
*/
ProjectList				Project::m_listProjects;


/**
	Constructor.
	
	@param		a_strFilePath		path of file to be parsed.
*/
/*Project::Project(std::string a_strFilePath )
{
	parse(a_strFilePath );
}*/


/**
	Constructor.
	
	@param		a_strFilePath		path of file to be parsed.
*/
Project::Project(void )
{}


/**
	Destructor.
	
*/
Project::~Project() throw()
{}


/**
	Instantiator for <code>Project</code> objects.
	
	@param		a_strFilePath		path of build file.
	@return		pointer to created <code>Project</code> object.
*/
Project*	Project::createProject(const std::string& a_strFilePath )
{
	//MW:	Attempt to create the top-level document...
	TiXmlDocument			doc(a_strFilePath );
	if (! doc.LoadFile() )
	{
		throw ParserException("failed to open build file '" + a_strFilePath + "' for reading" );
	}

	//MW:	Validate the root element...
	TiXmlElement*			elemRoot		= doc.RootElement();
	if (elemRoot->ValueStr() != STR_TAG_PROJECT )
	{
		throw ParserException("tag name '" + elemRoot->ValueStr() + "' is not a valid root element", elemRoot->Row() );
	}

	//MW:	Must have a valid name...
	const char*				pszName			= elemRoot->Attribute(STR_ATTRIB_NAME );
	std::string				strName			= (pszName ) ? pszName : STR_EMPTY;
	if (strName == STR_EMPTY )
	{
		throw ParserException("project must have a name" );
	}

	//MW:	Only load the project if the name isn't found in list of loaded
	//		projects...
	Project*				project			= 0x0;
	if (! Project::getProjectList().contains(strName ) )
	{
		project			 = new Project;
		Project::getProjectList().add(strName, project );
		
		project->parse(elemRoot );
	}
	
	return	project;
}
		
		
/**
	Parses the contents of an <i>XML</i> file.

	@param		a_elemRoot		root element of project.
*/
void	Project::parse(TiXmlElement* a_elemRoot ) throw(ParserException )
{
	//MW:	The name of the project...
	m_strName				= a_elemRoot->Attribute(STR_ATTRIB_NAME );

	//MW:	The project's attributes...
	const char*				pszBaseDir		= a_elemRoot->Attribute(STR_ATTRIB_BASEDIR );
	m_strBaseDirectory		= (pszBaseDir ) ? pszBaseDir : STR_EMPTY;
	const char*				pszDefault		= a_elemRoot->Attribute(STR_ATTRIB_DEFAULT );
	m_strDefaultTarget		= (pszDefault ) ? pszDefault : STR_EMPTY;
	const char*				pszDescription	= a_elemRoot->Attribute(STR_ATTRIB_DESCRIPTION );
	m_strDescription		= (pszDescription ) ? pszDescription : STR_EMPTY;

	//MW:	Iterate through the docs elements...
	TiXmlElement*			elemCurrent		= static_cast<TiXmlElement*>(a_elemRoot->FirstChild() );
	while (elemCurrent )
	{
		if (elemCurrent->Type() != TiXmlNode::COMMENT )
		{
			//MW:	Which type of element is this..?
			std::string			strName			= elemCurrent->ValueStr();
			if (strName == STR_TAG_INCLUDE )
			{
				//MW:	Get the path of the file to be included...
				const char*		pszIncludePath	= elemCurrent->Attribute(STR_ATTRIB_INCLUDE_PATH );
				std::string		strIncludePath	= (pszIncludePath ) ? pszIncludePath : STR_EMPTY;
				
				//MW:	Create an 'include' tag - this will parse all possible
				//		file-path elements...
				ProjectInclude	include(elemCurrent );
			
				//MW:	Instantiate a new Project object to load the
				//		included file...
				Project::createProject(include.getPath() );
			}
			else if (strName == STR_TAG_PROPERTY )
			{
				//MW:	Convert to usable property tag...
				ProjectProperty		tagProperty(elemCurrent );
				std::string			strProperty	= tagProperty.getAttribute(STR_ATTRIB_NAME );
				if (strProperty.size() == 0 )
				{
					throw ParserException("property must have a name", tagProperty.getLineNumber() );
				}
	
				//MW:	Store this property...
				m_mapProperties.insert(typePropertyMap::value_type(strProperty, tagProperty ) );
			}
			else if (strName == STR_TAG_TARGET )
			{
				//MW:	Convert to usable target tag...
				ProjectTarget		tagTarget(elemCurrent );
				std::string			strTarget	= tagTarget.getAttribute(STR_ATTRIB_NAME );
				if (strTarget.size() == 0 )
				{
					throw ParserException("target must have a name", tagTarget.getLineNumber() );
				}
		
				//MW:	Store this target...
				m_mapTargets.insert(typeTargetMap::value_type(strTarget, tagTarget ) );
			}
			else
			{
				throw ParserException("unrecognised element '" + strName + "'", elemCurrent->Row() );
			}
		}

		elemCurrent			= static_cast<TiXmlElement*>(a_elemRoot->IterateChildren(elemCurrent ) );
	}
}
		
		
/**
	Gives a named property.
	
	Prior to being returned, the property is evaluated by expanding all
	property references.
	
	@param		a_strName			name of property.
*/
std::string	Project::getProperty(const std::string& a_strName )
{
	//MW:	Split into a namespace/name pair...
	StringTuple				tuple;
	splitName(a_strName, tuple );
	
	return	evaluate(getProjectList().get(tuple.valueX )->m_mapProperties[tuple.valueY ].getValue() );
}
		
		
/**
	Gives a map of all properties.
	
*/
typePropertyMap&	Project::getProperties(void )
{
	return	m_mapProperties;
}


/**
	Gives a named target.
			
	@param		a_strName			name of target.
	@return		named target.
	
	@throws		<code>TagException</code> if the named target is not found.
*/
ProjectTarget	Project::getTarget(const std::string& a_strName )
{
	//MW:	Split into a namespace/name pair...
	StringTuple					tuple;
	splitName(a_strName, tuple );
	
	//MW:	Get the map of targets in the given namespace...
	typeTargetMap				mapTargets			= getProjectList().get(tuple.valueX )->getTargets();
	
	//MW:	Check to ensure that the named target exists...
	typeTargetMap::iterator		iter				= mapTargets.find(tuple.valueY );
	if (iter == mapTargets.end() )
	{
		throw TagException("named target '" + tuple.valueY + "' does not exist in namespace '" + tuple.valueX + "'" );
	}
	
	return	mapTargets[tuple.valueY ];
}
		
		
/**
	Gives a map of all targets.
	
*/
typeTargetMap&	Project::getTargets(void )
{
	return	m_mapTargets;
}
		
		
/**
	Indexer to give a named target.
	
*/
ProjectTarget	Project::operator[](const std::string& a_strName )
{
	return	getTarget(a_strName );
}


/**
	Evaluates a string, expanding all properties.
	
	@param		a_strUnexpanded		unexpanded string.
	@return		expanded string.
*/
std::string	Project::evaluate(const std::string& a_strUnexpanded )
{
	//MW:	Expanded string that will be built...
	std::string						strResult;
	
	//MW:	The size of the property start & end markers...
	static std::string::size_type	iSizePropStart	= STR_PROPERTY_START.size();
	static std::string::size_type	iSizePropEnd	= STR_PROPERTY_END.size();
	
	//MW:	Loop through the string to find the start of a property marker...
	std::string::size_type			iLastPos		= 0;
	std::string::size_type			iPos			= 0;
	while ((iPos = a_strUnexpanded.find(STR_PROPERTY_START, iLastPos ) ) != std::string::npos )
	{
		std::string::size_type		iNameStart		= iPos + iSizePropStart;
		std::string::size_type		iEndPos			= a_strUnexpanded.find(STR_PROPERTY_END, iNameStart );
		if (iEndPos == std::string::npos )
		{
			throw PropertyException("unterminated property in '" + a_strUnexpanded + "'" );
		}
		
		//MW:	The name & value of the property to be expanded...
		std::string					strName			= a_strUnexpanded.substr(iNameStart, iEndPos -iNameStart );
		std::string					strValue		= getProperty(strName );
		
		//MW:	Chop up & rebuild the string...
		strResult					+= a_strUnexpanded.substr(iLastPos, iPos -iLastPos ) + strValue;
		
		//MW:	Move past the inserted value - note that moving to this
		//		position means that any properties in the inserted
		//		property _won't_ be expanded. This is a deliberate
		//		decision but may be changed at some point...
		iLastPos					= iEndPos + iSizePropEnd;
	}
	
	strResult				+= a_strUnexpanded.substr(iLastPos );
	
	return	strResult;
}
		
		
/**
	Splits a given name into a namespace/name pair.
	
	The expected form is <code>namespace:name</code>. If the separator
	(<code>:</code>) is not found, the namespace is assumed to be the
	current one.
	
	@param		a_strName		name to be split.
	@param		a_tuple			tuple that will contain the name and
								namespace.
*/
void	Project::splitName(const std::string& a_strName, StringTuple& a_tuple )
{
	//MW:	Find the separator (if present)...
	std::string::size_type		iPos				= a_strName.find(CHAR_SEPARATOR_PAIR, 0 );
	if (iPos != std::string::npos )
	{
		a_tuple.valueX			= a_strName.substr(0, iPos );
		a_tuple.valueY			= a_strName.substr(++iPos );
	}
	else
	{
		a_tuple.valueX			= getName();
		a_tuple.valueY			= a_strName;
	}
}


/**
	Gives the base-directory.
	
	@see		m_strBaseDirectory.
	
	@return		base-directory.
*/
std::string	Project::getBaseDirectory(void ) const
{
	return	m_strBaseDirectory;
}


/**
	Gives the default target.
	
	@see		m_strDefaultTarget
	
	@return		default target.
*/
std::string	Project::getDefaultTarget(void ) const
{
	return	m_strDefaultTarget;
}


/**
	Gives the description.
	
	@see		m_strDescription
	
	@return		description.
*/
std::string	Project::getDescription(void ) const
{
	return	m_strDescription;
}


/**
	Gives the name.
	
	@see		m_strName
	
	@return		name.
*/
std::string	Project::getName(void ) const
{
	return	m_strName;
}


/**
	Gives a list of all loaded <code>Project</code> objects.
	
	@return		list of all loaded <code>Project</code> objects.
*/
ProjectList&	Project::getProjectList(void )
{
	return	m_listProjects;
}
